﻿using Abp.AspNetCore;
using Abp.AspNetCore.Configuration;
using Abp.AspNetCore.Mvc.Antiforgery;
using Abp.AspNetCore.Mvc.Extensions;
using Abp.AspNetCore.SignalR.Hubs;
using Abp.Auditing;
using Abp.Castle.Logging.Log4Net;
using Abp.Dependency;
using Abp.Extensions;
using Abp.Hangfire;
using Abp.PlugIns;
using Abp.Threading.BackgroundWorkers;
using Castle.Facilities.Logging;
using Hangfire;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.WebEncoders;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using Mt.Site.Core;
using Mt.Site.Core.Authorization;
using Mt.Site.Core.Base.BlobStoring;
using Mt.Site.Core.Base.BlobStoring.Aliyun;
using Mt.Site.Core.Base.BlobStoring.FileSystem;
using Mt.Site.Core.Base.Identity;
using Mt.Site.Core.Configuration;
using Mt.Site.Core.Url;
using Mt.Site.Core.Web;
using Mt.Site.Core.Web.Constraint;
using Mt.Site.EntityFrameworkCore;
using Mt.Site.Web.Authentication.JwtBearer;
using Mt.Site.Web.Chat.SignalR;
using Mt.Site.Web.Common;
using Mt.Site.Web.Filters;
using Mt.Site.Web.HealthCheck;
using Mt.Site.Web.IdentityServer;
using Mt.Site.Web.Resources;
using Mt.Site.Web.Swagger;
using Mt.Site.Web.Url;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.Transactions;
using HealthChecksUISettings = HealthChecks.UI.Configuration.Settings;
using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory;

namespace Mt.Site.Web.Startup
{
    public class Startup
    {
        private const string _defaultCorsPolicyName = "localhost";
        private const string _apiVersion = "v1";

        private readonly IConfigurationRoot _appConfiguration;
        private readonly IWebHostEnvironment _hostingEnvironment;

        public Startup(IWebHostEnvironment env)
        {
            _hostingEnvironment = env;
            _appConfiguration = env.GetAppConfiguration();
        }

        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            services.Configure<WebEncoderOptions>(options => options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));
            services.AddTransient<IUrlHelper, UrlGetter>();
            
            services.SniffRouteData();
            //MVC
            services.AddControllersWithViews(options =>
            {
                //options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
                options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
                options.Filters.AddService(typeof(StaticPageFilterAttribute));
                //options.Filters.Add(new CorsAuthorizationFilterFactory(DefaultCorsPolicyName));
            })
            .AddRazorRuntimeCompilation()
            .AddNewtonsoftJson();


            services.AddSignalR();

            //Configure CORS for angular2 UI
            services.AddCors(options =>
            {
                options.AddPolicy(_defaultCorsPolicyName, builder =>
                {
                    //App:CorsOrigins in appsettings.json can contain more than one address with splitted by comma.
                    builder
                        .WithOrigins(
                            // App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
                            _appConfiguration["App:CorsOrigins"]
                                .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                .Select(o => o.RemovePostFix("/"))
                                .ToArray()
                        )
                        .SetIsOriginAllowedToAllowWildcardSubdomains()
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials()
                        .SetPreflightMaxAge(TimeSpan.FromSeconds(3600));
                });
            });


            if (bool.Parse(_appConfiguration["KestrelServer:IsEnabled"]))
            {
                ConfigureKestrel(services);
            }

            IdentityRegistrar.Register(services);
            AuthConfigurer.Configure(services, _appConfiguration);

            //Identity server
            if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
            {
                IdentityServerRegistrar.Register(services, _appConfiguration);
            }

            services.ConfigCookieAuthenticationOptions();


            if (WebConsts.SwaggerUiEnabled)
            {
                //Swagger - Enable this line and the related lines in Configure method to enable swagger UI
                ConfigureSwagger(services);
            }

            //Recaptcha
            //services.AddreCAPTCHAV3(x =>
            //{
            //    x.SiteKey = _appConfiguration["Recaptcha:SiteKey"];
            //    x.SiteSecret = _appConfiguration["Recaptcha:SecretKey"];
            //});

            if (WebConsts.HangfireDashboardEnabled)
            {
                //Hangfire(Enable to use Hangfire instead of default job manager)
                services.AddHangfire(config =>
                {
                    config.UseSqlServerStorage(_appConfiguration.GetConnectionStringByAutoDecrypt(MtSiteConsts2.ConnectionStringName));

                    //config.UseStorage(
                    //    new MySqlStorage(
                    //        _appConfiguration.GetConnectionStringByAutoDecrypt(MtSiteConsts2.ConnectionStringName),
                    //        new MySqlStorageOptions
                    //        {
                    //            TransactionIsolationLevel = IsolationLevel.ReadCommitted,
                    //            QueuePollInterval = TimeSpan.FromSeconds(15),
                    //            JobExpirationCheckInterval = TimeSpan.FromHours(1),
                    //            CountersAggregateInterval = TimeSpan.FromMinutes(5),
                    //            PrepareSchemaIfNecessary = true,
                    //            DashboardJobListLimit = 50000,
                    //            TransactionTimeout = TimeSpan.FromMinutes(1),
                    //            TablesPrefix = "Hangfire"
                    //        }));
                });
            }

            services.AddScoped<IWebResourceManager, WebResourceManager>();

            if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
            {
                services.AddAbpZeroHealthCheck();

                var healthCheckUISection = _appConfiguration.GetSection("HealthChecks")?.GetSection("HealthChecksUI");

                if (bool.Parse(healthCheckUISection["HealthChecksUIEnabled"]))
                {
                     services.Configure<HealthChecksUISettings>(settings =>
                     {
                         healthCheckUISection.Bind(settings, c => c.BindNonPublicProperties = true);
                     });
                    services.AddHealthChecksUI()
                        .AddInMemoryStorage();
                }
            }

            //services.Configure<RazorViewEngineOptions>(options =>
            //{
            //    options.ViewLocationExpanders.Add(new RazorViewLocationExpander());
            //});

            ConfigBlobStoring(services);

            services.AddTransient<IClientInfoProvider, MtClientInfoProvider>();

            //Configure Abp and Dependency Injection
            return services.AddAbp<MtSiteWebModule>(options =>
            {
                //Configure Log4Net logging
                options.IocManager.IocContainer.AddFacility<LoggingFacility>(
                    f => f.UseAbpLog4Net().WithConfig(_hostingEnvironment.IsDevelopment()
                            ? "log4net.config"
                            : "log4net.Production.config")
                );

                options.PlugInSources.AddFolder(Path.Combine(_hostingEnvironment.WebRootPath, "plugins"),
                    SearchOption.AllDirectories);
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
        {
            //Initializes ABP framework.
            app.UseAbp(options =>
            {
                options.UseAbpRequestLocalization = false; //used below: UseAbpRequestLocalization
            });

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseStatusCodePagesWithReExecute("/Error", "?statusCode={0}");
                //app.UseStatusCodePagesWithRedirects("~/Error?statusCode={0}");
                app.UseExceptionHandler("/Error"); 
            }

            if (bool.Parse(_appConfiguration["App:RewriteUrlsByMap"]))
            {
                app.UseRewriteUrlsByMap();
            }

            if (bool.Parse(_appConfiguration["App:RewriteToHttps"]))
            {
                app.UseHttpsRedirection();
            }
            if (bool.Parse(_appConfiguration["App:RewriteToIndexPage"]))
            {
                app.UseRewriteToIndexPage();
            }

            app.UseMtUrlRewite();

            app.UseStaticFiles();

            app.UseRouting();

            app.UseCors(_defaultCorsPolicyName); //Enable CORS!

            app.UseAuthentication();
            app.UseJwtTokenMiddleware();

            if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
            {
                app.UseJwtTokenMiddleware("IdentityBearer");
                app.UseIdentityServer();
            }

            app.UseAuthorization();

            using (var scope = app.ApplicationServices.CreateScope())
            {
                if (scope.ServiceProvider.GetService<DatabaseCheckHelper>()
                    .Exist(_appConfiguration.GetConnectionStringByAutoDecrypt(MtSiteConsts2.ConnectionStringName)))
                {
                    app.UseMtRequestLocalization();
                }
            }

            if (WebConsts.HangfireDashboardEnabled)
            {
                //Hangfire dashboard & server (Enable to use Hangfire instead of default job manager)
                app.UseHangfireDashboard(WebConsts.HangfireDashboardEndPoint, new DashboardOptions
                {
                    Authorization = new[] { new AbpHangfireAuthorizationFilter(AppPermissions.Pages_Administration_HangfireDashboard) }
                });

                //TODO: 解决  WorkerCount的问题
                app.UseHangfireServer(new BackgroundJobServerOptions()
                {
                    WorkerCount = 1
                });

                GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 5 });
                GlobalJobFilters.Filters.Add(new CheckJobExceptionAttribute());
            }

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<AbpCommonHub>("/signalr");
                endpoints.MapHub<ChatHub>("/signalr-chat");

                endpoints.MapControllerRoute(
                    name: "areas",
                    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
                    );

                endpoints.MapControllerRoute(
                    name: "Default3",
                    pattern: "{tenancyName}/{cultureName}/{controller}/{action}/{kind}/{relateId}/{id}/{page}",
                    defaults: new
                    {
                        controller = "Home",
                        action = "Index",
                        kind = 0,
                        page = "page_1"
                    },
                    constraints: new
                    {
                        tenancyName = new TenancyNameConstraint(),
                        cultureName = new CultureNameConstraint(),
                        kind = new LongRouteConstraint(),
                        relateId = new IdContraint(),
                        id = new IdContraint(),
                        page = new PageIndexContraint()
                    }
                );

                endpoints.MapControllerRoute(
                    name: "Default",
                    pattern: "{tenancyName}/{cultureName}/{controller}/{action}/{kind}/{id}",
                    defaults: new
                    {
                        controller = "Home",
                        action = "Index",
                        kind = 0
                    },
                    constraints: new
                    {
                        tenancyName = new TenancyNameConstraint(),
                        cultureName = new CultureNameConstraint(),
                        kind = new LongRouteConstraint(),
                        id = new IdContraint()
                    }
                );

                endpoints.MapControllerRoute(
                    name: "Default2",
                    pattern: "{tenancyName}/{cultureName}/{controller}/{action}/{kind}/{page}",
                    defaults: new
                    {
                        controller = "Home",
                        action = "Index",
                        kind = 0,
                        page = "page_1"
                    },
                    constraints: new
                    {
                        tenancyName = new TenancyNameConstraint(),
                        cultureName = new CultureNameConstraint(),
                        kind = new LongRouteConstraint(),
                        page = new PageIndexContraint()
                    }
                );

                endpoints.MapControllerRoute(
                    name: "Default3",
                    pattern: "{controller}/{action}",
                    defaults: new { }
                );


                if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
                {
                    endpoints.MapHealthChecks("/health", new HealthCheckOptions()
                    {
                        Predicate = _ => true,
                        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
                    });
                }

                app.ApplicationServices.GetRequiredService<IAbpAspNetCoreConfiguration>().EndpointConfiguration.ConfigureAllEndpoints(endpoints);
            });
            if (app.Properties.TryGetValue("__EndpointRouteBuilder", out var obj))
            {
                var capture = app.ApplicationServices.GetRequiredService<RouteCapture>();
                capture.EndpointRouteBuilder = (IEndpointRouteBuilder)obj!;
            }

            if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksEnabled"]))
            {
                if (bool.Parse(_appConfiguration["HealthChecks:HealthChecksUI:HealthChecksUIEnabled"]))
                {
                    app.UseHealthChecksUI();
                }
            }

            if (WebConsts.SwaggerUiEnabled)
            {
                // Enable middleware to serve generated Swagger as a JSON endpoint
                //app.UseSwagger(c => { c.RouteTemplate = "swagger/{documentName}/swagger.json"; });
                app.UseSwagger();
                // Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)

                app.UseSwaggerUI(options =>
                {
                    options.SwaggerEndpoint(_appConfiguration["App:SwaggerEndPoint"], $"MtSite API {_apiVersion}");
                    options.IndexStream = () => Assembly.GetExecutingAssembly()
                        .GetManifestResourceStream("Mt.Site.Web.wwwroot.swagger.ui.index.html");
                    options.InjectBaseUrl(_appConfiguration["App:ServerRootAddress"]);
                    // Controls the display of the request duration (in milliseconds) for "Try it out" requests.
                    //options.DisplayRequestDuration(); 
                }); //URL: /swagger
            }
        }

        private void ConfigureKestrel(IServiceCollection services)
        {
            // Kestrel 是微软推出的唯一真正实现跨平台的 Web 服务器 
            // https://www.cnblogs.com/artech/p/KestrelServer.html   
            services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
            {
                options.Listen(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 443),
                    listenOptions =>
                    {
                        var certPassword = _appConfiguration.GetValue<string>("Kestrel:Certificates:Default:Password");
                        var certPath = _appConfiguration.GetValue<string>("Kestrel:Certificates:Default:Path");
                        var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certPath, 
                            certPassword);
                        listenOptions.UseHttps(new HttpsConnectionAdapterOptions()
                        {
                            ServerCertificate = cert
                        });
                    });
            });
        }

        private void ConfigBlobStoring(IServiceCollection services)
        {
            var isEnabled = _appConfiguration.GetValue<bool>("Aliyun:IsEnabled"); 
            var accessKeyId = _appConfiguration["Aliyun:AccessKeyId"];
            var accessKeySecret = _appConfiguration["Aliyun:AccessKeySecret"];
            var endpoint = _appConfiguration["Aliyun:Endpoint"];
            var regionId = _appConfiguration["Aliyun:RegionId"];
            var roleArn = _appConfiguration["Aliyun:RoleArn"];
            string bucketName = _appConfiguration["Aliyun:Bucket"];
            //string temporaryCredentialsCacheKey = "297A96094D7048DBB2C28C3FDB20839A";

            services.Configure<AbpBlobStoringOptions>(options =>
            {
                options.Containers.ConfigureAll((containerName, containerConfiguration) =>
                {
                    if(isEnabled)
                    {
                        containerConfiguration.UseAliyun(aliyun =>
                        {
                            aliyun.AccessKeyId = accessKeyId;
                            aliyun.AccessKeySecret = accessKeySecret;
                            aliyun.Endpoint = endpoint;
                            //STS
                            aliyun.UseSecurityTokenService = false;
                            //aliyun.RegionId = regionId;
                            //aliyun.RoleArn = roleArn;
                            //aliyun.RoleSessionName = Guid.NewGuid().ToString("N");
                            //aliyun.DurationSeconds = 900;
                            //aliyun.Policy = string.Empty;
                            //Other
                            aliyun.CreateContainerIfNotExists = false;
                            aliyun.ContainerName = bucketName;
                            //aliyun.TemporaryCredentialsCacheKey = temporaryCredentialsCacheKey;
                        });
                    }
                    else
                    {
                        containerConfiguration.UseFileSystem(fileSystem =>
                        {
                            fileSystem.BasePath = "~/mtsc/uploads";
                            fileSystem.AppendContainerNameToBasePath = false;
                        });
                    }
                });
            });
        }

        private void ConfigureSwagger(IServiceCollection services)
        {
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc(_apiVersion, new OpenApiInfo() { Title = "MtSite API", Version = _apiVersion });
                options.DocInclusionPredicate((docName, description) => true);
                options.ParameterFilter<SwaggerEnumParameterFilter>();
                options.SchemaFilter<SwaggerEnumSchemaFilter>();
                options.OperationFilter<SwaggerOperationIdFilter>();
                options.OperationFilter<SwaggerOperationFilter>();
                options.CustomDefaultSchemaIdSelector();

                //options.CustomSchemaIds(type => type.FullName); // 相同类名会报错的问题

                //add summaries to swagger
                bool canShowSummaries = _appConfiguration.GetValue<bool>("Swagger:ShowSummaries");
                if (canShowSummaries)
                {
                    var hostXmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                    var hostXmlPath = Path.Combine(AppContext.BaseDirectory, hostXmlFile);
                    options.IncludeXmlComments(hostXmlPath);

                    var applicationXml = $"Mt.Site.Application.xml";
                    var applicationXmlPath = Path.Combine(AppContext.BaseDirectory, applicationXml);
                    options.IncludeXmlComments(applicationXmlPath);

                    var applicationBaseXml = $"Mt.Site.Application.Base.xml";
                    var applicationBaseXmlPath = Path.Combine(AppContext.BaseDirectory, applicationBaseXml);
                    options.IncludeXmlComments(applicationBaseXmlPath);

                    var webCoreXmlFile = $"MyCompanyName.AbpZeroTemplate.Web.Core.xml";
                    var webCoreXmlPath = Path.Combine(AppContext.BaseDirectory, webCoreXmlFile);
                    options.IncludeXmlComments(webCoreXmlPath);
                }
            }).AddSwaggerGenNewtonsoftSupport();
        }
    }
}
